/* Janus Chat - Version 1.0
 * Authors: Matthew Perry
 *          Michael Freid
 *          Marc Latou
 *          Richard Lee
 *
 * The Janus Chat Client is a highly secured
 * peer-to-peer chat client designed to facilitate
 * the transfer of sensitive data or as a communication
 * medium for the extra paranoid
 *
 * This file contains the implementation for a wrapper
 * of the crypto++ RSA library so that Janus can encrypt
 * its session keys for AES message encryption */

#include "jRsa.h"

#include <cryptopp/rsa.h>
using CryptoPP::RSA;
using CryptoPP::InvertibleRSAFunction;
using CryptoPP::RSAES_OAEP_SHA_Encryptor;
using CryptoPP::RSAES_OAEP_SHA_Decryptor;

#include <cryptopp/filters.h>
using CryptoPP::StringSink;
using CryptoPP::StringSource;
using CryptoPP::PK_EncryptorFilter;
using CryptoPP::PK_DecryptorFilter;

#include <cryptopp/osrng.h>
using CryptoPP::AutoSeededRandomPool;

#include <cryptopp/integer.h>
using CryptoPP::Integer;

#include <string>
using std::string;

#include <utility>
using std::pair;
using std::make_pair;

#include "exceptions.h"

pair<jRsa::publicKey,jRsa::privateKey> jRsa::generateKeys( unsigned int length )
{
    AutoSeededRandomPool rng;

    InvertibleRSAFunction parameters;
    parameters.GenerateRandomWithKeySize( rng, length );

    RSA::PrivateKey privateKey( parameters );
    RSA::PublicKey publicKey( parameters );
  
    return make_pair( publicKey,privateKey );
}

string jRsa::encrypt( const string& message, publicKey& key )
{
    string cipher;
    AutoSeededRandomPool rng;
  
    RSAES_OAEP_SHA_Encryptor e( key );
  
    StringSource( message, true, new PK_EncryptorFilter( rng, e,new StringSink( cipher ) ) );
  
    return cipher;
}

string jRsa::decrypt( const string& cipher, privateKey& key )
{
    string output;
    try
    {
        AutoSeededRandomPool rng;

        RSAES_OAEP_SHA_Decryptor d( key );

        StringSource( cipher, true, new PK_DecryptorFilter( rng, d, new StringSink( output ) ) ); // StringSource

    }
    catch( CryptoPP::Exception& e )
    {
        string msg = "Decryption Failed: ";
        string details( e.what() );
        throw new MessageException( msg + details );
    }
  
    return output;
}


string jRsa::encoder( char* data, unsigned int n, unsigned int e  )
{
    char encoding[8 + n + e ];

    for( int i = 3; i >= 0; --i )
        encoding[i] = char( n >> ( ( 3 - i ) * 8 ) );

    for( int i = 7; i >= 4; --i )
        encoding[i] = char( e >> ( ( 7 - i ) * 8 ) );

    for( unsigned int i = 0; i < ( n + e ); ++i )
        encoding[i + 8] = data[i];

    string retval( encoding, n + e + 8 );
    return retval;
}

string jRsa::serializePublicKey( const publicKey& key )
{
  
    unsigned int nLength = key.GetModulus().MinEncodedSize();
    unsigned int eLength = key.GetPublicExponent().MinEncodedSize();

    byte* data = new byte[nLength+eLength];
  
    key.GetModulus().Encode(data, nLength);
    key.GetPublicExponent().Encode(data+nLength,eLength);

    return encoder( (char*)data, nLength, eLength );
}

jRsa::publicKey jRsa::recoverKey( const string& sKey )
{
    if( sKey.size() < 137 )
        throw new MessageException( "Serial key is too small" );

    string header = sKey.substr( 0, 8 );
    int nLength = 0;
    for( int i = 0; i < 4; ++i )
        nLength = (nLength << 8) | (unsigned char)header[i];

    int eLength = 0;
    for( int i = 4; i < 8; ++i )
        eLength = (eLength << 8) | (unsigned char)header[i];

    string data = sKey.substr( 8, nLength + eLength );

    byte* n = new byte[nLength];
    byte* e = new byte[eLength];

    for(int i=0;i<nLength;i++)
        n[i] = (byte) ( data[i]);

    for(int i=0;i<eLength;i++)
        e[i] = (byte) (data[i+nLength]);

    Integer nValue( n ,nLength);
    Integer eValue( e ,eLength);
  
    jRsa::publicKey key;

    key.Initialize(nValue, eValue);
  
    return key;
}

